package com.flca.dbs.jpa;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.flca.frw.IEntity;
import com.flca.frw.dao.DaoException;
import com.flca.frw.dao.util.IPaging;

/**
 * helper class for Jpa dao factionality.
 * @author robin bakkerus
 *
 * @param <T1>
 * @param <T2>
 */
public class JpaDaoHelper<T1, T2> {

	protected static Logger logger = LoggerFactory.getLogger(JpaDaoHelper.class);

	private EntityManager em;

	/**
	 * this function is called by the DaoJpaFactory to create a Dao impl
	 * @param aEm
	 */
	public JpaDaoHelper(EntityManager aEm) {
		em = aEm;
	}

	/**
	 * This save's the given IEntity. If the pk is null the entity will be
	 * inserted, otherwise the existing entity will be updated. The pk of the new
	 * or existing object will be returned. TODO return the object and update the
	 * cartridge XxxDaoImpl
	 * 
	 * @param aSource
	 * @param ctx
	 * @return
	 * @throws DaoException
	 */
	@SuppressWarnings("unchecked")
	public T2 save(IEntity aSource) throws DaoException {
		logDebug("save " + aSource);
		try {
			if (aSource instanceof IEntity) {
				Object result = em.merge(aSource);
				em.flush();
				return (T2) ((IEntity) result).getId();
			} else {
				throw new RuntimeException("DaoJpa.save() input object is not a IEnum but "
						+ aSource.getClass().getName());
			}
		} catch (Exception e) {
			logger.error("error in DaoHelper.save :  of " + aSource.toString(), e);
			throw new DaoException(e);
		}
	}

	/**
	 * Return all entities of the given Entity
	 * 
	 * @param aClass
	 * @param ctx
	 * @return
	 * @throws DaoException
	 */
	@SuppressWarnings("unchecked")
	public Iterator<T1> findAll(Class<?> aClass) throws DaoException {
		logDebug("findall " + aClass);
		try {
			String queryString = "select x from " + aClass.getName() + " x";
			Query query = em.createQuery(queryString);
			return (Iterator<T1>) query.getResultList().iterator();
		} catch (Exception e) {
			logger.error("error in DaoHelper.findAll() of " + aClass);
			throw new DaoException(e);
		}
	}

	/**
	 * return an Entity given the primary key (pk)
	 * 
	 * @param aClass
	 * @param aId
	 * @param ctx
	 * @return
	 * @throws DaoException
	 */
	@SuppressWarnings("unchecked")
	public T1 retrieve(Class<?> aClass, T2 aId) throws DaoException {
		try {
//			logDebug("aId " + aId.getClass() + "  " + aId);
			if (aId != null) {
				Long pk = new Long(aId.toString());
				return (T1) em.find(aClass, pk);
			} else {
				logError("DaoHelper.retrieveById() of  class:" + aClass + " called with null ");
				return null;
			}
		} catch (Exception e) {
			logger.error("Error in DaoHelper.retrieveById() of  class:" + aClass + " aId: " + aId, e);
			throw new DaoException(e);
		}
	}

	/**
	 * remove an Entity given the primary key pk()
	 * 
	 * @param aClass
	 * @param aPk
	 * @param ctx
	 * @throws DaoException
	 */
	public void removeById(Class<?> aClass, T2 aPk) throws DaoException {
		try {
			Object obj = em.find(aClass, aPk);
			em.remove(obj);
		} catch (Exception e) {
			logger.error("error in DaoHelper.removeById() of " + aClass);
			throw new DaoException(e);
		}
	}

	public IPaging<T1> pagingSearch(IPaging<T1> aArgs, String aQuery, Map<String, Object> aParams) throws DaoException {
		IPaging<T1> results = aArgs;

		try {
			String countQry = "select count(*) " + aQuery;
			Query query = em.createQuery(countQry);
			for (String param : aParams.keySet()) {
				query.setParameter(param, aParams.get(param));
			}
			Long objCount = (Long) query.getSingleResult();

			query = em.createQuery(aQuery);
			for (String param : aParams.keySet()) {
				query.setParameter(param, aParams.get(param));
			}

			if (aArgs.getPageSize() > 0) {
				query.setFirstResult(aArgs.getPageNumber() * aArgs.getPageSize());
				query.setMaxResults(aArgs.getPageSize());
				int n = Math.round(objCount.intValue() / aArgs.getPageSize());
				results.setPageCount(n + 1);
			}

			// results.setResults(query.getResultList()); TODO
			return results;
		} catch (Exception e) {
			logger.error("error in DaoHelper.removeById() of ");
			throw new DaoException(e);
		}
	}

	public Iterator<?> openByQuery(Class<?> aClass, String aSql, Object[] aArguments) throws Exception {
		if (em != null && em.getTransaction() != null) {
			Query qry = em.createQuery(aSql);
			if (aArguments != null) {
				for (int i = 0; i < aArguments.length; i++) {
					qry.setParameter(i + 1, aArguments[i]);
				}
			}
			List<?> list = qry.getResultList();
			return list.iterator();
		} else {
			throw new NullPointerException("openByQuery called with EntityManager null");
		}
	}

	public Iterator<?> openByQuery(String aSql, Object[] aArguments) throws Exception {
		if (em != null && em.getTransaction() != null) {
			Query qry = em.createQuery(aSql);
			if (aArguments != null) {
				for (int i = 0; i < aArguments.length; i++) {
					qry.setParameter(i, aArguments[i]);
				}
			}
			List<?> list = qry.getResultList();
			return list.iterator();
		} else {
			throw new NullPointerException("openByQuery called with EntityManager null");
		}
	}

	public void deleteAll(Class<?> aClass) throws Exception {
		logError("not implemented yet");
	}

	
	private void logDebug(String msg) {
		if (logger.isDebugEnabled()) {
			logger.debug(msg);
		}
	}
	
	private void logError(String msg) {
		logger.error(msg); //TODO add ctx info
	}

}
